package leetcode.sort;

import java.util.Arrays;

/**
 * @author shilei
 * @create 2021-12-09 19:12
 * 基数排序（桶排序）演示
 * 1、基数排序属于“分配式排序” 又称 “桶子法” 或 bin sort 顾名思义，它是通过键值的各个位的值，将要排序的元素分配至某些 桶 中，达到排序的作用
 * 2、基数排序法是属于稳定性的排序，基数排序法的是效率高的稳定性排序法
 * 3、基数排序是桶排序的扩展
 * 4、基数排序是1887年赫尔曼-何乐礼发明的。它是这样实现的：将整数按照位数切割成不同的数字，然后按每个位数分别比较。
 * 需要开辟额外相同的数据大小的桶来存储
 * <p>
 *     基数排序说明：
 *      1、基数排序是对传统桶排序的扩展、速度很快
 *      2、技术排序是经典的空间换时间的方式，占用内存很大，当对海量数据排序时，容易造成OutOfMemoryError
 *      3、基数排序是稳定的。如果出现相同数据时，排序前排序后位置是一致的
 *      4、基数排序时如果当前排序数组中出现负数会有数组下标越界问题，需要对负数求绝对值或者数组中存在负数时不适用基数排序
 *
 * 基数排序 10万数据随机排序  20毫秒左右
 * 基数排序 100万数据随机排序  100毫秒左右
 * 基数排序 1000万数据随机排序  737毫秒左右
 */
public class RadixSort {
    public static void main(String[] args) {
//        int[] arr = {53, 3, 542, 748, 14, 214};
//        raidxSort(arr);
//        raidxSortFinal(arr);

        int[] arr = new int[10000000];
        for (int i = 0; i < 10000000; i++) {
            arr[i] = (int) (Math.random() * 10000000);
        }
        long begin = System.currentTimeMillis();
        raidxSortFinal(arr);
        long end = System.currentTimeMillis();
        System.out.println(end - begin);
    }

    public static void raidxSortFinal(int[] arr) {
        //最终基数排序代码
        //1.得到数组中最大的位数
        int max = arr[0];//假设第一个就是最大的数  但不一定
        for (int i = 1; i < arr.length; i++) {
            //如果arr[i] 大于假定的最大值 就修改最大值为当前值
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        //得到最大数是几位数 使用string的length 来判断是几位数
        int maxLength = (max + "").length();
        /*
            说明：
                1、二维数组包含10个一维数组
                2、为了防止在放入数的时候，数据溢出，则每个一维数组（桶）大小定为arr.length
                3、明确 基数排序是使用空间换时间的经典算法
         */
        int[][] bucket = new int[10][arr.length];
        //为了记录每个桶中 实际存放了多少个数据 我们定义一个一维数组来记录各个桶的每次放入的数据个数
        //可以这样理解 比如：bucketElementCounts[0] 记录的就是 bucket[0] 桶的放入数据个数
        int[] bucketElementCounts = new int[10];
        //使用for循环遍历当前最大数的位数 几位数则需要遍历几轮
        for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
            for (int j = 0; j < arr.length; j++) {
                //取出每个元素的个位的值
                int digitOfElement = arr[j] / n % 10;
                //放入到对应的桶中
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                bucketElementCounts[digitOfElement]++;
            }
            //按照这个桶的顺序（一维数组的下标依次取出数据 放入原来数组）
            int index = 0;
            //遍历每一桶 并将桶中的数据 放入到原数组
            for (int k = 0; k < bucketElementCounts.length; k++) {
                //如果桶中 有数据 我们才放入到原数组
                if (bucketElementCounts[k] != 0) {
                    //循环该桶即第k个桶（即第k个一维数组） 放入
                    for (int l = 0; l < bucketElementCounts[k]; l++) {
                        //取出元素放入到arr
                        arr[index++] = bucket[k][l];
                    }
                }
                //第i+1轮之后需要将桶中 bucketElementCounts 清空
                bucketElementCounts[k] = 0;
            }
//            System.out.println("第"+(i+1)+"轮之后：" + Arrays.toString(arr));
        }

    }

    /**
     * 基数排序 方法
     *
     * @param arr 原始数组
     */
    public static void raidxSort(int[] arr) {
        //第一轮 针对每个元素的个位进行排序处理
        //定义一个二维数组 表示10个桶 每个桶就是一个一维数组
        /*
            说明：
                1、二维数组包含10个一维数组
                2、为了防止在放入数的时候，数据溢出，则每个一维数组（桶）大小定为arr.length
                3、明确 基数排序是使用空间换时间的经典算法
         */
        int[][] bucket = new int[10][arr.length];
        //为了记录每个桶中 实际存放了多少个数据 我们定义一个一维数组来记录各个桶的每次放入的数据个数
        //可以这样理解 比如：bucketElementCounts[0] 记录的就是 bucket[0] 桶的放入数据个数
        int[] bucketElementCounts = new int[10];

        //第一轮 针对每个元素的个位进行排序处理
        for (int j = 0; j < arr.length; j++) {
            //取出每个元素的个位的值
            int digitOfElement = arr[j] % 10;
            //放入到对应的桶中
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            bucketElementCounts[digitOfElement]++;
        }
        //按照这个桶的顺序（一维数组的下标依次取出数据 放入原来数组）
        int index = 0;
        //遍历每一桶 并将桶中的数据 放入到原数组
        for (int k = 0; k < bucketElementCounts.length; k++) {
            //如果桶中 有数据 我们才放入到原数组
            if (bucketElementCounts[k] != 0) {
                //循环该桶即第k个桶（即第k个一维数组） 放入
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    //取出元素放入到arr
                    arr[index++] = bucket[k][l];
                }
            }
            //第一轮之后需要将桶中 bucketElementCounts 清空
            bucketElementCounts[k] = 0;
        }
        System.out.println("第一轮之后：" + Arrays.toString(arr));

        //第二轮排序==========================
        for (int j = 0; j < arr.length; j++) {
            //取出每个元素的十位的值
            int digitOfElement = arr[j] / 10 % 10;
            //放入到对应的桶中
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            bucketElementCounts[digitOfElement]++;
        }
        //按照这个桶的顺序（一维数组的下标依次取出数据 放入原来数组）
        index = 0;
        //遍历每一桶 并将桶中的数据 放入到原数组
        for (int k = 0; k < bucketElementCounts.length; k++) {
            //如果桶中 有数据 我们才放入到原数组
            if (bucketElementCounts[k] != 0) {
                //循环该桶即第k个桶（即第k个一维数组） 放入
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    //取出元素放入到arr
                    arr[index++] = bucket[k][l];
                }
            }
            //第二轮之后需要将桶中 bucketElementCounts 清空
            bucketElementCounts[k] = 0;
        }
        System.out.println("第二轮之后：" + Arrays.toString(arr));

        //第三轮排序==========================
        for (int j = 0; j < arr.length; j++) {
            //取出每个元素的百位的值
            int digitOfElement = arr[j] / 100 % 10;
            //放入到对应的桶中
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            bucketElementCounts[digitOfElement]++;
        }
        //按照这个桶的顺序（一维数组的下标依次取出数据 放入原来数组）
        index = 0;
        //遍历每一桶 并将桶中的数据 放入到原数组
        for (int k = 0; k < bucketElementCounts.length; k++) {
            //如果桶中 有数据 我们才放入到原数组
            if (bucketElementCounts[k] != 0) {
                //循环该桶即第k个桶（即第k个一维数组） 放入
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    //取出元素放入到arr
                    arr[index++] = bucket[k][l];
                }
            }
            //第三轮之后需要将桶中 bucketElementCounts 清空
            bucketElementCounts[k] = 0;
        }
        System.out.println("第三轮之后：" + Arrays.toString(arr));
    }
}
